6.2 channel

在并发编程中,共享变量一直都是一个大问题,也是经常容易出问题的地方。

那么在Go语言中就提出了channel,也就是通道的概念,用以解决协程间的通信问题。

本节我们将对channel进行讲解。

本节代码存放目录为 lesson17

什么是cahnnel

channel我们可以将其理解为通道,那么就说明它是一个:一端进一端出的概念。

那么在并发编程中,就可以用来连接多个协程。比如说一个协程往通道中写数据,一个协程从通道的另一端读数据。

其实也就是一个类似队列的东西,这样就可以保证数据不会冲突,我们可以将需要处理的业务、数据等通过通道发送,再由多个协程来进行消耗。

使用channel

channel分为有缓冲与无缓冲两种。

有缓冲的通道也就是指:这个通道是可以放多个数据的,就算读取端还没有读取,直到放满为止。

无缓冲的通道指:这个通道只可以放一个数据,只有当读取端将这个数据读取完成之后,发送端才可以继续发送数据,否则就是阻塞的。

channel的使用也比较简单,我们可以结合上一节所学习到的goroutine一起来学习。代码如下所示:

var (
    numberChan chan int
)
numberChan = make(chan int)

go func() {
    for i := 0; i < 100; i++ {
        fmt.Printf("写入数据: %d\n", i)
        numberChan <- i
        time.Sleep(time.Duration(1) * time.Second)
    }
}()

go func() {
    for number := range numberChan {
        fmt.Printf("读取数据: %d\n", number)
    }
}()

select {}

在上面的代码中,我们首先声明了一个无缓冲的通道numberChan,需要注意的是,channel变量的声明与之前章节学习的map是一致的。

之后我们使用go func开启了一个写数据的协程,向通道中写入数据使用的是:numberChan <- i,也就是符号:<-

同时我们开启了一个读取数据的协程,从通道中读取数据,我们这里使用了for range读取,这就表示一直不停的读取。

如果我们只是单次读取,那么我们可以这样:

number := <-numberChan

在上面的示例中,我们用的是无缓冲的通道,因为我们没有定义通道的大小,那么有缓冲的通道应该如何使用呢?如下代码所示:

var (
    numberBufferChan chan int
)
numberBufferChan = make(chan int, 5)

与之前所不同的是,我们在声明变量,使用make的时候,我们增加了一个数字5,这就表示通道的缓冲长度为5

也就是说,如果没有协程在读取的时候,我们最多可以向通道中写入5个数据。

需要注意的是,当通道使用完成之后,我们需要进行关闭。如下代码所示:

close(numberBufferChan)

在上面的代码中,我们使用close()对通道进行了关闭,通道关闭后读取端将读取不到数据。

截止目前,我们的程序是可以正常运行的,但是会发现在关闭通道后,可能会出现报错。

那么我们增加了另一个概念:WaitGroup,这是Go语言中用于管理一组协程的东西。

它会等待所有协程执行完毕之后才会进行退出,同时还可以实现协程的优雅退出。更新代码如下:

var (
    wg         sync.WaitGroup
    numberChan chan int
)
numberChan = make(chan int)

wg.Add(1)
go func() {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        fmt.Printf("写入数据: %d\n", i)
        numberChan <- i
        time.Sleep(time.Duration(1) * time.Second)
    }
    close(numberChan)
}()

wg.Add(1)
go func() {
    defer wg.Done()
    for number := range numberChan {
        fmt.Printf("读取数据: %d\n", number)
    }
}()

var (
    numberBufferChan chan int
)
numberBufferChan = make(chan int, 5)

wg.Add(1)
go func() {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        fmt.Printf("写入数据Buffer: %d\n", i)
        numberBufferChan <- i
        time.Sleep(time.Duration(1) * time.Second)
    }
    close(numberBufferChan)
}()

wg.Add(1)
go func() {
    defer wg.Done()
    for number := range numberBufferChan {
        fmt.Printf("读取数据Buffer: %d\n", number)
    }
}()

wg.Wait()

小结

channel是实现并发的重要组成部分,使用channel我们可以更加安全的进行并发编程。

关于本节总结如下:

  • channel用于协程之间的数据同步

  • 分为有缓冲通道与无缓冲通道

  • 无缓冲通道在没有读取时会阻塞

  • 必须使用make进行通道的初始化

  • 使用chan <- i进行通道数据的发送

  • 通道传递的数据可以是任意类型的

  • 使用close()对通道进行关闭

results matching ""

    No results matching ""